1 /* 2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021 3 License: [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License]. 4 Authors: Marcelo S. N. Mancini 5 6 Copyright Marcelo S. N. Mancini 2018 - 2021. 7 Distributed under the CC BY-4.0 License. 8 (See accompanying file LICENSE.txt or copy at 9 https://creativecommons.org/licenses/by/4.0/ 10 */ 11 12 module hip.systems.game; 13 import hip.global.gamedef; 14 import hip.hipaudio.audio; 15 import hip.view; 16 import hip.error.handler; 17 import hip.api.view.scene; 18 19 import hip.systems.timer_manager; 20 import hip.event.dispatcher; 21 import hip.event.handlers.keyboard; 22 import hip.event.handlers.mouse; 23 import hip.windowing.events; 24 import hip.hiprenderer.renderer; 25 import hip.graphics.g2d.renderer2d; 26 public import hip.event.handlers.input_listener; 27 28 version(WebAssembly) version = CustomRuntime; 29 version(CustomRuntimeTest) version = CustomRuntime; 30 version(PSVita) version = CustomRuntime; 31 32 version(Load_DScript) 33 { 34 import hip.systems.hotload; 35 import hip.systems.compilewatcher; 36 private bool getDubError(string line) 37 { 38 import hip.console.log; 39 import hip.util.string:indexOf, lastIndexOf; 40 int errInd = line.indexOf("Warning:"); 41 if(errInd == -1) errInd = line.indexOf("Error:"); //Check first for warnings 42 return errInd != -1; 43 } 44 extern(System) AScene function() HipremeEngineGameInit; 45 extern(System) void function() HipremeEngineGameDestroy; 46 } 47 48 49 class GameSystem 50 { 51 /** 52 * Holds the member that generates the events as inputs 53 */ 54 EventDispatcher dispatcher; 55 AScene[] scenes; 56 57 HipInputListener inputListener; 58 HipInputListener scriptInputListener; 59 string projectDir, buildCommand = "dub build --parallel --skip-registry=all"; 60 ///Resets delta time after a reload for not jumping frames. 61 protected bool shouldResetDelta; 62 protected __gshared AScene externalScene; 63 64 version(Load_DScript) 65 { 66 static CompileWatcher watcher; 67 protected static HotloadableDLL hotload; 68 } 69 bool hasFinished; 70 float fps = 0; 71 float targetFPS = 0; 72 float fpsAccumulator = 0; 73 size_t frames = 0; 74 75 this(float targetFPS) 76 { 77 this.targetFPS = targetFPS; 78 dispatcher = new EventDispatcher(HipRenderer.window); 79 dispatcher.addOnResizeListener((uint width, uint height) 80 { 81 HipRenderer.width = width; 82 HipRenderer.height = height; 83 resizeRenderer2D(width, height); 84 foreach (AScene s; scenes) 85 s.onResize(width, height); 86 }); 87 inputListener = new HipInputListener(dispatcher); 88 scriptInputListener = new HipInputListener(dispatcher); 89 inputListener.addKeyboardListener(HipKey.ESCAPE, 90 (meta){hasFinished = true;} 91 ); 92 inputListener.addKeyboardListener(HipKey.F1, 93 (meta){import hip.bind.interpreters; reloadInterpreter();}, 94 HipButtonType.up 95 ); 96 97 version(Load_DScript) 98 { 99 inputListener.addKeyboardListener(HipKey.F5,(meta) 100 { 101 import hip.console.log; 102 rawlog("Recompiling and Reloading game "); 103 recompileReloadExternalScene(); 104 }, HipButtonType.up 105 ); 106 } 107 108 } 109 110 void loadGame(string gameDll, string buildCommand) 111 { 112 version(Load_DScript) 113 { 114 import hip.filesystem.hipfs; 115 import hip.util.path; 116 import hip.util.system; 117 import hip.util.string:indexOf; 118 import hip.console.log; 119 120 121 if(gameDll.isAbsolutePath && HipFS.absoluteIsFile(gameDll)) 122 { 123 projectDir = gameDll.dirName; 124 } 125 else if(!gameDll.extension && gameDll.indexOf("projects/") == -1) 126 { 127 projectDir = joinPath("projects", gameDll); 128 gameDll = joinPath("projects", gameDll, gameDll); 129 } 130 else 131 projectDir = gameDll; 132 133 watcher = new CompileWatcher(projectDir, null, ["d"]).run; 134 this.buildCommand = buildCommand ? buildCommand : this.buildCommand; 135 136 hotload = new HotloadableDLL(gameDll, (void* lib) 137 { 138 ErrorHandler.assertLazyExit(lib != null, "No library " ~ gameDll ~ " was found"); 139 HipremeEngineGameInit = 140 cast(typeof(HipremeEngineGameInit)) 141 dynamicLibrarySymbolLink(lib, "HipremeEngineGameInit"); 142 ErrorHandler.assertLazyExit(HipremeEngineGameInit != null, 143 "HipremeEngineGameInit wasn't found when looking into "~gameDll); 144 HipremeEngineGameDestroy = 145 cast(typeof(HipremeEngineGameDestroy)) 146 dynamicLibrarySymbolLink(lib, "HipremeEngineGameDestroy"); 147 ErrorHandler.assertLazyExit(HipremeEngineGameDestroy != null, 148 "HipremeEngineGameDestroy wasn't found when looking into "~gameDll); 149 }); 150 } 151 } 152 153 void recompileGame() 154 { 155 version(Load_DScript) 156 { 157 import std.process:pipeShell, Redirect, wait; 158 import hip.console.log; 159 auto dub = pipeShell("cd "~projectDir~" && "~buildCommand, Redirect.stderrToStdout | Redirect.stdout); 160 161 scope string[] errors; 162 foreach(l; dub.stdout.byLine) 163 { 164 if(getDubError(cast(string)l)) 165 errors~= l.idup; 166 else logln(cast(string)l); 167 } 168 int status = wait(dub.pid); 169 //2 == up to date 170 if(errors.length) 171 { 172 loglnError(errors); 173 foreach(err; errors) loglnError(err); 174 } 175 else 176 hotload.reload(); 177 } 178 } 179 180 void startGame() 181 { 182 version(Test) 183 { 184 // addScene(new SoundTestScene()); 185 // addScene(new ChainTestScene()); 186 // addScene(new AssetTest()); 187 import hip.view.testscene; 188 import hip.console.log; 189 import hip.api.data.commons; 190 mixin LoadReferencedAssets!(["hip.view.testscene"]); 191 loadReferenced; 192 hiplog("starting test scene."); 193 addScene(new TestScene()); 194 } 195 else version(Load_DScript) 196 { 197 ErrorHandler.assertExit(HipremeEngineGameInit != null, "No game was loaded"); 198 externalScene = HipremeEngineGameInit(); 199 addScene(externalScene); 200 } 201 else version(Standalone) 202 { 203 import gamescript.entry; 204 externalScene = HipremeEngineMainScene(); 205 addScene(externalScene); 206 } 207 } 208 209 void recompileReloadExternalScene() 210 { 211 version(Load_DScript) 212 { 213 import hip.util.array:remove; 214 import hip.console.log; 215 if(hotload) 216 { 217 shouldResetDelta = true; 218 rawlog("Recompiling game"); 219 HipTimerManager.clearSchedule(); 220 scriptInputListener.clearAll(); 221 HipremeEngineGameDestroy(); 222 scenes.remove(externalScene); 223 externalScene = null; 224 recompileGame(); // Calls hotload.reload(); 225 startGame(); 226 } 227 } 228 } 229 230 /** 231 * Adding a scene will initialize them, while checking for assets referencing for auto loading them. 232 */ 233 void addScene(AScene s) 234 { 235 import hip.assetmanager; 236 HipAssetManager.addOnLoadingFinish(() 237 { 238 import hip.console.log; 239 version(CustomRuntime) 240 { 241 s.preload(); 242 loglnWarn("Initializing scene ", s.getName); 243 s.initialize(); 244 scenes~= s; 245 } 246 else 247 { 248 try{ 249 s.preload(); 250 loglnWarn("Initializing scene ", s.getName); 251 s.initialize(); 252 scenes~= s; 253 } 254 catch (Error e){scriptFatalError(e);} 255 } 256 }); 257 } 258 259 bool update(float deltaTime) 260 { 261 import hip.assetmanager; 262 import std.stdio; 263 frames++; 264 fpsAccumulator+= deltaTime; 265 if(shouldResetDelta) 266 { 267 deltaTime = 0; 268 shouldResetDelta = false; 269 } 270 if(fpsAccumulator >= 1.0) 271 { 272 import hip.console.log; 273 // logln("FPS: ", frames); 274 frames = 0; 275 fpsAccumulator = 0; 276 } 277 HipAudio.update(); 278 HipTimerManager.update(deltaTime); 279 HipAssetManager.update(); 280 281 version(Load_DScript) 282 { 283 if(watcher.update()) 284 recompileReloadExternalScene(); 285 } 286 dispatcher.handleEvent(); 287 dispatcher.pollGamepads(deltaTime); 288 inputListener.update(); 289 scriptInputListener.update(); 290 291 if(hasFinished || dispatcher.hasQuit) 292 return false; 293 foreach(s; scenes) 294 { 295 import hip.console.log; 296 version(CustomRuntime) 297 { 298 if(s is null) logln("SCENE IS NULL"); 299 else s.update(deltaTime); 300 } 301 else 302 { 303 try 304 { 305 if(s is null) logln("SCENE IS NULL"); 306 else s.update(deltaTime); 307 } 308 catch (Error e){scriptFatalError(e);} 309 } 310 } 311 312 return true; 313 } 314 315 version(CustomRuntime){} 316 else 317 void scriptFatalError(Throwable e, string file = __FILE__, size_t line = __LINE__, string func = __PRETTY_FUNCTION__) 318 { 319 import hip.console.log; 320 import hip.util.path; 321 loglnError(e.msg, ". Project: (", projectDir, ") at file (", e.file, ":",e.line, ")"); 322 quit(); 323 ErrorHandler.assertExit(false, "Script Fatal Error", file, line, __MODULE__, func); 324 } 325 void render() 326 { 327 version(CustomRuntime) 328 { 329 foreach (AScene s; scenes) 330 s.render(); 331 } 332 else 333 { 334 try 335 { 336 foreach (AScene s; scenes) 337 s.render(); 338 } 339 catch(Throwable e){scriptFatalError(e);} 340 } 341 HipTimerManager.render(); 342 } 343 void postUpdate() 344 { 345 dispatcher.postUpdate(); 346 } 347 348 void quit() 349 { 350 version(Load_DScript) 351 { 352 if(hotload !is null) 353 { 354 if(HipremeEngineGameDestroy != null) 355 HipremeEngineGameDestroy(); 356 scenes.length = 0; 357 externalScene = null; 358 hotload.dispose(); 359 watcher.stop(); 360 } 361 } 362 import hip.assetmanager; 363 HipAssetManager.dispose(); 364 365 } 366 }